package com.nth.android.image;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;

/**
 * A Drawable object that draws primitive shapes. A ShapeDrawable takes a
 * {@link android.graphics.drawable.shapes.Shape} object and manages its
 * presence on the screen. If no Shape is given, then the ShapeDrawable will
 * default to a {@link android.graphics.drawable.shapes.RectShape}.
 * 
 * <p>
 * This object can be defined in an XML file with the <code>&lt;shape></code>
 * element.
 * </p>
 * 
 * 
 * @attr ref android.R.styleable#ShapeDrawablePadding_left
 * @attr ref android.R.styleable#ShapeDrawablePadding_top
 * @attr ref android.R.styleable#ShapeDrawablePadding_right
 * @attr ref android.R.styleable#ShapeDrawablePadding_bottom
 * @attr ref android.R.styleable#ShapeDrawable_color
 * @attr ref android.R.styleable#ShapeDrawable_width
 * @attr ref android.R.styleable#ShapeDrawable_height
 */
public class ProgressDrawable extends Drawable {

	private ProgressState mProgressState;
	private boolean mMutated;

	/**
	 * ShapeDrawable constructor.
	 */
	public ProgressDrawable() {
		this((ProgressState) null);
		mProgressState.mBytesCurrent = 0;
		mProgressState.mBytesTotal = 0;
	}

	/**
	 * Creates a ShapeDrawable with a specified Shape.
	 * 
	 * @param s
	 *            the Shape that this ShapeDrawable should be
	 */
	public ProgressDrawable(long bytesCurrent, long bytesTotal) {
		this((ProgressState) null);

		mProgressState.mBytesCurrent = bytesCurrent;
		mProgressState.mBytesTotal = bytesTotal;
	}

	private ProgressDrawable(ProgressState state) {
		mProgressState = new ProgressState(state);
	}

	public long getBytesCurrent() {
		return mProgressState.mBytesCurrent;
	}

	public void setBytesCurrent(long bytesCurrent) {
		mProgressState.mBytesCurrent = bytesCurrent;
		updateDrawable();
	}

	public long getBytesTotal() {
		return mProgressState.mBytesTotal;
	}

	public void setBytesTotal(long bytesTotal) {
		mProgressState.mBytesTotal = bytesTotal;
		updateDrawable();
	}

	/**
	 * Returns the Paint used to draw the shape.
	 */
	public Paint getFillPaint() {
		return mProgressState.mFillPaint;
	}

	public Paint getTextPaint() {
		return mProgressState.mTextPaint;
	}

	/**
	 * Sets the intrinsic (default) width for this shape.
	 * 
	 * @param width
	 *            the intrinsic width (in pixels)
	 */
	public void setIntrinsicWidth(int width) {
		mProgressState.mIntrinsicWidth = width;
		invalidateSelf();
	}

	/**
	 * Sets the intrinsic (default) height for this shape.
	 * 
	 * @param height
	 *            the intrinsic height (in pixels)
	 */
	public void setIntrinsicHeight(int height) {
		mProgressState.mIntrinsicHeight = height;
		invalidateSelf();
	}

	@Override
	public int getIntrinsicWidth() {
		return mProgressState.mIntrinsicWidth;
	}

	@Override
	public int getIntrinsicHeight() {
		return mProgressState.mIntrinsicHeight;
	}

	private static int modulateAlpha(int paintAlpha, int alpha) {
		int scale = alpha + (alpha >>> 7); // convert to 0..256
		return paintAlpha * scale >>> 8;
	}

	@Override
	public void draw(Canvas canvas) {
		Rect r = getBounds();
		Paint paint = mProgressState.mFillPaint;

		int prevAlpha = paint.getAlpha();
		paint.setAlpha(modulateAlpha(prevAlpha, mProgressState.mAlpha));

		int count = canvas.save();
		canvas.translate(r.left, r.top);

		drawProgress(canvas, mProgressState.mFillPaint, mProgressState.mTextPaint, mProgressState.mBytesCurrent, mProgressState.mBytesTotal);

		canvas.restoreToCount(count);

		paint.setAlpha(prevAlpha);
	}

	private void drawProgress(Canvas canvas, Paint fillPaint, Paint textPaint, long mBytesCurrent, long mBytesTotal) {
		final float ratio = (float) mProgressState.mBytesCurrent / mProgressState.mBytesTotal;
		final float sweepAngle = ratio * 360f;
		final RectF rect = new RectF(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
		final Paint p2 = new Paint(fillPaint);
		p2.setAlpha(128);
		textPaint.setTextAlign(Align.CENTER);

		canvas.drawArc(rect, 0, sweepAngle, true, fillPaint);
		canvas.drawOval(rect, p2);
		canvas.drawText(Math.round(ratio * 100) + "%", rect.centerX(), rect.centerY() + textPaint.getTextSize() / 2, textPaint);
	}

	@Override
	public int getChangingConfigurations() {
		return super.getChangingConfigurations() | mProgressState.mChangingConfigurations;
	}

	/**
	 * Set the alpha level for this drawable [0..255]. Note that this drawable
	 * also has a color in its paint, which has an alpha as well. These two
	 * values are automatically combined during drawing. Thus if the color's
	 * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then
	 * the combined alpha that will be used during drawing will be 37.5% (i.e.
	 * 96).
	 */
	@Override
	public void setAlpha(int alpha) {
		mProgressState.mAlpha = alpha;
		invalidateSelf();
	}

	@Override
	public void setColorFilter(ColorFilter cf) {
		mProgressState.mFillPaint.setColorFilter(cf);
		invalidateSelf();
	}

	@Override
	public int getOpacity() {
		return PixelFormat.TRANSLUCENT;
	}

	@Override
	public void setDither(boolean dither) {
		mProgressState.mFillPaint.setDither(dither);
		invalidateSelf();
	}

	@Override
	protected void onBoundsChange(Rect bounds) {
		super.onBoundsChange(bounds);
		updateDrawable();
	}

	private void updateDrawable() {
		final Rect r = getBounds();
		setIntrinsicWidth(r.width());
		setIntrinsicHeight(r.height());
		invalidateSelf();
	}

	@Override
	public ConstantState getConstantState() {
		mProgressState.mChangingConfigurations = getChangingConfigurations();
		return mProgressState;
	}

	@Override
	public Drawable mutate() {
		if (!mMutated && super.mutate() == this) {
			mProgressState.mFillPaint = new Paint(mProgressState.mFillPaint);
			mProgressState.mTextPaint = new Paint(mProgressState.mTextPaint);
			mProgressState.mBytesCurrent = mProgressState.mBytesCurrent;
			mProgressState.mBytesTotal = mProgressState.mBytesTotal;
			mMutated = true;
		}
		return this;
	}

	/**
	 * Defines the intrinsic properties of this ShapeDrawable's Shape.
	 */
	final static class ProgressState extends ConstantState {
		int mChangingConfigurations;
		Paint mFillPaint;
		Paint mTextPaint;
		long mBytesCurrent;
		long mBytesTotal;
		int mIntrinsicWidth;
		int mIntrinsicHeight;
		int mAlpha = 255;

		ProgressState(ProgressState orig) {
			if (orig != null) {
				mFillPaint = orig.mFillPaint;
				mTextPaint = orig.mTextPaint;
				mBytesCurrent = orig.mBytesCurrent;
				mBytesTotal = orig.mBytesTotal;
				mIntrinsicWidth = orig.mIntrinsicWidth;
				mIntrinsicHeight = orig.mIntrinsicHeight;
				mAlpha = orig.mAlpha;
			} else {
				mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
				mFillPaint.setStyle(Style.FILL);
				mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
				mBytesCurrent = 0;
				mBytesTotal = 0;
			}
		}

		@Override
		public Drawable newDrawable() {
			return new ProgressDrawable(this);
		}

		@Override
		public Drawable newDrawable(Resources res) {
			return new ProgressDrawable(this);
		}

		@Override
		public int getChangingConfigurations() {
			return mChangingConfigurations;
		}
	}

}
